using System;
using System.Runtime.Serialization;
using System.Collections.Generic;
using Innovation.Serialization;
using System.Reflection;
using Microsoft.Xna.Framework;

namespace Innovation
{
    // The base class of all objects in the engine. It contains methods
    // for drawing, updating, etc. It also is Serializable, meaning its
    // state can be saved to a binary file for saving and loading
    public class Component
    {
	    // The GameScreen object that owns this component
        public GameScreen Parent;

	    // Whether or not this component has been initialized
        public bool Initialized = false;

    	// Whether or not the GameScreen that owns the component
	    // should draw it
        public bool Visible = true;

        // The draw order of the component. Lower values draw first
        int drawOrder = 1;

        // Draw order changed event
        public event ComponentDrawOrderChangedEventHandler DrawOrderChanged;

        // The component's name
        public string Name;

        // Whether or not the engine should serialize this component
        public bool Serialize = true;

        // Public draw order. If the value is changed, we fire the draw
        // order change event
        public int DrawOrder
        {
            get { return drawOrder;}
            set
            {
                // Save a copy of the old value and set the new one
                int last = drawOrder;
                drawOrder = value;

                // Fire DrawOrderChanged
                if (DrawOrderChanged != null)
                    DrawOrderChanged(this, 
                        new ComponentDrawOrderChangedEventArgs(
                            this, last, this.Parent.Components));
            }
        }

	    // This overloaded constructor allows us to specify the parent
        public Component(GameScreen Parent)
        {
            InitializeComponent(Parent);
        }

	    // This overload allows will set the parent to the default
	    // GameScreen
        public Component()
        {
            InitializeComponent(Engine.DefaultScreen);
        }

        static Random nameRand = new Random();

	    // This is called by the constructor to initialize the
	    // component. This allows us to only have to override this
	    // method instead of both constructors
        protected virtual void InitializeComponent(GameScreen Parent)
        {
            // Check if the engine has been initialized before setting
            // up the component, or the Engine will crash when the
            // component tries to add itself to the list.
            if (!Engine.IsInitialized)
                throw new Exception("Engine must be initialized with \"SetupEngine()\"" 
                    + "before components can be initialized");

            // Register the component with its parent and show that it
            // is done loading
            Parent.Components.Add(this);
            Initialized = true;

            string randomNumber =
                nameRand.Next(1, 9).ToString() +
                nameRand.Next(1, 9).ToString() +
                nameRand.Next(1, 9).ToString() +
                nameRand.Next(1, 9).ToString() +
                nameRand.Next(1, 9).ToString();

            // Set the name to "ComponentNumber-ComponentType"
            Name = this.GetType().Name + "-" + randomNumber;
        }

	    // Updates the component - This is called by the owner
        public virtual void Update()
        {
        }

        // Allows the component to do any drawing it needs to do that
        // can potentially mess up the GraphicsDevice (by setting
        // render states, render targets, etc)
        public virtual void PreDraw()
        {
        }

	    // Draws the component - This is called by the owner
        public virtual void Draw()
        {
        }

    	// Unregisters the component with its parent
        public virtual void DisableComponent()
        {
            Parent.Components.Remove(this);
        }

        // Change ToString to return the component's name
        public override string ToString()
        {
            return this.Name;
        }

        // Returns a SerializationData a Serializer can use to save the state
        // of the object to an Xml file
        public SerializationData GetSerializationData(Serializer Serializer)
        {
            // Create a new SerializationData
            SerializationData data = new SerializationData(Serializer);

            // Add the basic Component values
            data.AddData("Component.Name", Name);
            data.AddData("Component.DrawOrder", DrawOrder);
            data.AddData("Component.ParentScreen", Parent.Name);
            data.AddData("Component.Visible", Visible);

            // Tell the serializer that it will need to know the type of
            // component
            data.AddDependency(this.GetType());

            // Construct a ServiceData
            ServiceData sd = new ServiceData();

            // If this object is a service, find out what the
            // provider type is (the type used to look up the service)
            Type serviceType;
            if (Engine.Services.IsService(this, out serviceType))
            {
                // Tell the serializer about the provider type
                data.AddDependency(serviceType);

                // Set the data to the ServiceData
                sd.IsService = true;
                sd.Type = serviceType.FullName;
            }

            // Add the ServiceData to the SerializationData
            data.AddData("Component.ServiceData", sd);

            // Call the overridable function that allows components to provide data
            SaveSerializationData(data);

            return data;
        }

        // Reconstructs the Component from SerializationData
        public void RecieveSerializationData(SerializationData Data)
        {
            // Set the basic Component values
            this.Name = Data.GetData<string>("Component.Name");
            this.DrawOrder = Data.GetData<int>("Component.DrawOrder");
            this.Visible = Data.GetData<bool>("Component.Visible");

            // Get the ServiceData from the data
            ServiceData sd = Data.GetData<ServiceData>("Component.ServiceData");

            // If the component was a service
            if (sd.IsService)
            {
                // Get the type back from the serializer
                Type t = Data.GetTypeFromDependency(sd.Type);

                // Add the service to the Engine
                Engine.Services.AddService(t, this);
            }

            // Set the owner GameScreen
            string parent = Data.GetData<string>("Component.ParentScreen");
            this.Parent = Engine.GameScreens[parent];

            // Call the overridable function that allow components to load from data
            LoadFromSerializationData(Data);
        }

        // Overridable function to allow components to save data during serialization
        public virtual void SaveSerializationData(SerializationData Data)
        {
        }

        // Overridable function to allow components to load data during deserialization
        public virtual void LoadFromSerializationData(SerializationData Data)
        {
        }
    }

    // Information about Services
    struct ServiceData
    {
        public string Type;
        public bool IsService;
    }

    // Provides a link between the Component class and Serializer class. A component
    // adds data and keys to this class to simplify serialization
    public class SerializationData
    {
        // Dictionary that stores the data. It is public so the Serializer can save
        // the data it holds.
        public Dictionary<string, object> Data = new Dictionary<string, object>();

        // Add data to the dictionary with the string key Key and value object Data
        public void AddData(string Key, object Data)
        {
            this.Data.Add(Key, Data);
        }

        // Get data of type T from the dictionary with the string key Key
        public T GetData<T>(string Key)
        {
            object val = this.Data[Key];
            return (T)val;
        }

        // Whether or not the data contains the specified Key
        public bool ContainsData(string Key)
        {
            return Data.ContainsKey(Key);
        }

        // The serializer being used for serialization/deserialization
        Serializer serializer;

        // Constructor sets the serializer being used
        public SerializationData(Serializer Serializer)
        {
            serializer = Serializer;
        }

        // Tell the serializer we will be using the specified Type
        public void AddDependency(Type type)
        {
            serializer.Dependency(type);
        }

        // Get the Type specified through the serializer, so the right
        // Assembly will be used.
        public Type GetTypeFromDependency(string type)
        {
            return Assembly.Load(serializer.DependencyMap[type]).GetType(type);
        }
    }

    // Event arguments for draw order change on a component
    public class ComponentDrawOrderChangedEventArgs : EventArgs
    {
        // Component that was modified
        public Component Component;

        // The old draw order
        public int LastDrawOrder;

        // The collection that owns the component
        public ComponentCollection ParentCollection;

        public ComponentDrawOrderChangedEventArgs(Component Component, 
            int LastDrawOrder, ComponentCollection ParentCollection)
        {
            this.Component = Component;
            this.LastDrawOrder = LastDrawOrder;
            this.ParentCollection = ParentCollection;
        }
    }

    // Event handler for draw order change on a component
    public delegate void ComponentDrawOrderChangedEventHandler(
        object sender, ComponentDrawOrderChangedEventArgs e);
}
